课程视频地址:https://study.163.com/courses-search?keyword=CS231

课程主页:http://cs231n.stanford.edu/2017/

这一讲主要介绍损失函数和优化。

损失函数

损失函数告诉我们分类器有多好,给定图片数据集$\{(x_i,y_i)\}_{i=1}^N$,$x_i$为图片,$y_i$为标签,定义我们的损失为

其中$L_i​$为损失函数。下面先介绍多类别支持向量机损失函数。(Multiclass SVM loss)

多类别支持向量机损失

然后定义SVM损失为

这个损失函数也被称为“折叶损失”(hinge loss),损失函数图像如下

这个函数首先判断图片的正确分类的得分$(s_{y_i})$是否超过其余分类得分($s_j $)加$1$,如果成立,则损失为$0$,否则损失为$s_j -s_{y_i}+1$。我们来看一个具体的例子

猫的$s_{y_i}=3.2​$,所以

类似的,我们可以计算出汽车以及青蛙的损失,结果如下

所以总损失为

接下来考虑如下几个问题。

1.如果汽车分数有稍微改变,损失函数会发生什么变化?

因为汽车的正确分数$s_{y_i}$比$s_j+1$大很多,所以稍微改变$s_{y_i}$不会改变损失。

2.损失函数的最大值和最小值分别为多少?

从图像中就可以看出,损失的最小值为$0$,最大为$\infty$。

3.在一开始$W​$很小,所以$s\approx 0​$,那么此时的损失函数大约是多少?

此时$ \max(0, s_j -s_{y_i}+1)\approx 1​$,所以由公式$L_i=\sum_{j\neq y_i} \max(0, s_j -s_{y_i}+1)​$可知,

其中$K​$为分类总数。这个结果通常用来检验代码是否出错。

4.如果损失函数的求和是遍历所有类别(包括$j=y_i$),那么损失函数为多少?

利用公式即可,此时损失函数是原有的损失函数加$1$。

5.如果我们损失函数采用均值而不是求和,那么会怎么样?

没有影响,最后只是相差常数倍而已。

6.如果我们使用

会怎么样?

这样会对异常值更加敏感。

7.如果我们找到$W$,使得

那么$W$是唯一的吗?

不是,这是因为

所以

如果

那么

从而$2W$也会使得$L=0$。

这个事实告诉我们应该对$W$也进行约束,于是定义如下损失

第一项称为数据损失,第二项称为正则化项,$\lambda$为超参数,比较常见的正则化项如下:

Softmax分类器

首先定义如下概率

我们想最大化对数似然函数,而这也等价于最小化如下式子

来看一个具体的例子:

接着来看如下几个问题。

1.损失函数$L_i​$的最大值,最小值分别是多少?

因为$P(Y= y_i|X= x_i)\in [0,1]$,所以$L_i \in [0,\infty)$

2.在一开始$W$很小,所以$s\approx 0$,那么此时的损失函数大约是多少?

此时$e^{s_j}\approx 1$,如假设有$K$类,那么

同样的,这个结果也通常用来检验代码是否出错。

把上述内容总结一下:

所以接下来的问题是如何找到最优的$W$,这就进入一下个主题——优化。

优化

现在的目标是找到最优的$W$,下面分别介绍两种方法。

1.随机搜索

该策略很简单,反复迭代,随机初始化$W$,然后比较其和当前最优解的效果即可。代码如下

# assume X_train is the data where each column is an example (e.g. 3073 x 50,000)
# assume Y_train are the labels (e.g. 1D array of 50,000)
# assume the function L evaluates the loss function

bestloss = float("inf") # Python assigns the highest possible float value
for num in xrange(1000):
  W = np.random.randn(10, 3073) * 0.0001 # generate random parameters
  loss = L(X_train, Y_train, W) # get the loss over the entire training set
  if loss < bestloss: # keep track of the best solution
    bestloss = loss
    bestW = W
  print 'in attempt %d the loss was %f, best %f' % (num, loss, bestloss)

# prints:
# in attempt 0 the loss was 9.401632, best 9.401632
# in attempt 1 the loss was 8.959668, best 8.959668
# in attempt 2 the loss was 9.044034, best 8.959668
# in attempt 3 the loss was 9.278948, best 8.959668
# in attempt 4 the loss was 8.857370, best 8.857370
# in attempt 5 the loss was 8.943151, best 8.857370
# in attempt 6 the loss was 8.605604, best 8.605604
# ... (trunctated: continues for 1000 lines)

(代码参考地址:https://cs231n.github.io/optimization-1/)

这种方法的效果很糟糕,正确率大约15%左右。

2.跟随梯度

这个策略的思路很简单,假设我们位于山谷,我们想最快到达谷底,很自然的想法就是沿着最陡的方向前进:

函数最陡的方向就是梯度方向了,一维的情形就是计算导数

高维的情形则需计算梯度(偏导数),利用定义计算数值梯度

但实际上,这个方法很不可取,因为数据维度往往非常大,所以计算一次梯度需要的时间很多,比较正确的方法是使用微积分直接求出梯度,所以我们需要计算

上述方法被称为梯度下降。但是注意这个方法依旧有一个问题,因为实际中数据量也非常大,所以如果按照上述公式计算一次梯度还是需要非常久,所以我们常使用小批量数据梯度下降,设定一个批量值batch,每次只计算batch个数据的梯度即可,在实际中,batch通常取32/64/128,代码如下

# Vanilla Minibatch Gradient Descent

while True:
  data_batch = sample_training_data(data, 256) # sample 256 examples
  weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
  weights += - step_size * weights_grad # perform parameter update

(代码参考地址:https://cs231n.github.io/optimization-1/)